package page;

import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.value.ObservableValue;
import javafx.concurrent.Worker.State;
import javafx.event.ActionEvent;
import javafx.geometry.HPos;
import javafx.geometry.Pos;
import javafx.geometry.VPos;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Hyperlink;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
import javafx.scene.paint.Color;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebView;
import javafx.stage.Stage;
import netscape.javascript.JSObject;
import org.apache.http.impl.client.BasicCookieStore;

import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.*;
import java.util.*;
import java.util.concurrent.locks.ReentrantLock;

public class WebViewSample extends Application {

    private Scene scene;

    @Override
    public void start(Stage stage) throws Exception {
        // create scene
        stage.setTitle("Web View Sample");
        scene = new Scene(new Browser(stage), 900, 600, Color.web("#666970"));
        stage.setScene(scene);
        // apply CSS style
        scene.getStylesheets().add("webviewsample/BrowserToolbar.css");
        // show stage
        stage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

class Browser extends Region {

    private final HBox toolBar;
//    final private static String[] imageFiles = new String[]{
//            "product.png",
//            "blog.png",
//            "documentation.png",
//            "partners.png",
//            "help.png"
//    };
    final private static String[] captions = new String[]{
            "Products",
            "Blogs",
            "Documentation",
            "Partners",
    };
    final private static String[] urls = new String[]{
            "http://www.oracle.com/products/index.html",
            "http://blogs.oracle.com/",
            "http://docs.oracle.com/javase/index.html",
            "http://www.oracle.com/partners/index.html"
    };
    final ImageView selectedImage = new ImageView();
    final Hyperlink[] hpls = new Hyperlink[captions.length];
//    final Image[] image = new Image[imageFiles.length];
    WebView browser = null;
    WebEngine webEngine = null;
    final Button toggleHelpTopics = new Button("Toggle Help Topics");
    private boolean needDocumentationButton = false;


    public Browser(final Stage stage) throws Exception {
        //apply the styles
        getStyleClass().add("browser");

        for (int i = 0; i < captions.length; i++) {
            // create hyperlinks
            Hyperlink hpl = hpls[i] = new Hyperlink(captions[i]);
//            Image image = image[i]
//                    = new Image(getClass().getResourceAsStream(imageFiles[i]));
//            hpl.setGraphic(new ImageView(image));
            final String url = urls[i];
            final boolean addButton = (hpl.getText().equals("Help"));

            // process event
            hpl.setOnAction((ActionEvent e) -> {
                needDocumentationButton = addButton;
                webEngine.load(url);
            });

        }

//        MyCookieStore cookie_store = new MyCookieStore();
//        CookieManager cookie_manager = new CookieManager(cookie_store, new MyCookiePolicy());
//        CookieHandler.setDefault(cookie_manager);



//        URI uri = URI.create("https://abs-in.idumiao.com");
//        Map<String, List<String>> headers = new LinkedHashMap<String, List<String>>();
//        headers.put("Set-Cookie", Arrays.asList("JSESSIONID=2DE8CF2DEC1EDFE2D68A96F306AE2B93;Domain=abs-in.idumiao.com;Path=/;Secure;",
//                "rememberMe=KzNqI5G5SHGllaMPeTuqnDapPd3YIKBkZ0UVENQoBbwjYebThvUzD0JopULLp1UW46HqfxRcZTGRzDj6/v7jby92ZlTMR4Q7gnQZNAU2WEyn38uOCHRoPWBF+8BmbjowIwG17wmhBTaj1JQjNpWGxLPDeToP9c9egzJsaQzZJCiUJBT6jocAZBVy0KzCxWLurTanp23/hhsZCWeI20fUP5lxvWmQPL+6mpOBhecLNyTjITuHThgfyBOIxwWmGME+QviXcvOJEovjMy1b7tA8zdK3CLs3KZnWnxpdr+djIKJRE0Vv0CzAO9DikQ1aOqHFpXE/w/n1arL+zMc20fHDL5ZyCvcAEECQgREBjrY6+ohsNrZP//9M4gMCANA1PAbW98tj6s2/cR2S34fh5x1awL/HOuF+9jawlF3oi5aoKPSKyXZjD3hRMU7ySoddMDC7x+T6KXvsCjf1rtDbveFfDr+pa0V8EiNGM/c9PyXBuwF7goRAHDPMeTFwvICm4DwThDhMCEIGMs0SiDfxgaesGFc3SNeml1kfrN/3yjIS6J8=;Domain=abs-in.idumiao.com; Max-Age=2018-12-05T10:40:26.090Z; Path=/;Secure;" ));
//        java.net.CookieHandler.getDefault().put(uri, headers);

        CookieStore cookieStore = new InMemoryCookieStore();
        CookieManager cookie_manager = new CookieManager(cookieStore, new MyCookiePolicy());
//        CookieHandler.setDefault(cookie_manager);
        HttpCookie cookie1 = new HttpCookie("rememberMe", "KzNqI5G5SHGllaMPeTuqnDapPd3YIKBkZ0UVENQoBbwjYebThvUzD0JopULLp1UW46HqfxRcZTGRzDj6/v7jby92ZlTMR4Q7gnQZNAU2WEyn38uOCHRoPWBF+8BmbjowIwG17wmhBTaj1JQjNpWGxLPDeToP9c9egzJsaQzZJCiUJBT6jocAZBVy0KzCxWLurTanp23/hhsZCWeI20fUP5lxvWmQPL+6mpOBhecLNyTjITuHThgfyBOIxwWmGME+QviXcvOJEovjMy1b7tA8zdK3CLs3KZnWnxpdr+djIKJRE0Vv0CzAO9DikQ1aOqHFpXE/w/n1arL+zMc20fHDL5ZyCvcAEECQgREBjrY6+ohsNrZP//9M4gMCANA1PAbW98tj6s2/cR2S34fh5x1awL/HOuF+9jawlF3oi5aoKPSKyXZjD3hRMU7ySoddMDC7x+T6KXvsCjf1rtDbveFfDr+pa0V8EiNGM/c9PyXBuwF7goRAHDPMeTFwvICm4DwThDhMCEIGMs0SiDfxgaesGFc3SNeml1kfrN/3yjIS6J8=");
        cookie1.setDomain("abs-in.idumiao.com");
        cookie1.setPath("/");
        cookie1.setSecure(true);
//        cookie_manager.getCookieStore().add(new URI("abs-in.idumiao.com"), cookie1);
        // create the toolbar
        toolBar = new HBox();
        toolBar.setAlignment(Pos.CENTER);
        toolBar.getStyleClass().add("browser-toolbar");
        toolBar.getChildren().addAll(hpls);
        toolBar.getChildren().add(createSpacer());

        //set action for the button
        toggleHelpTopics.setOnAction((ActionEvent t) -> {
            webEngine.executeScript("toggle_visibility('help_topics')");
        });

        browser = new WebView();
        webEngine = browser.getEngine();

        // process page loading
        webEngine.getLoadWorker().stateProperty().addListener(
                (ObservableValue<? extends State> ov, State oldState,
                 State newState) -> {
                    toolBar.getChildren().remove(toggleHelpTopics);
                    if (newState == State.SUCCEEDED) {
                        JSObject win
                                = (JSObject) webEngine.executeScript("window");
                        win.setMember("app", new JavaApp());
                        if (needDocumentationButton) {
                            toolBar.getChildren().add(toggleHelpTopics);
                        }



                    }
                    CookieHandler cookieHandler = CookieHandler.getDefault();
                    System.out.println(cookieHandler);
                    if (cookieHandler instanceof  com.sun.webkit.network.CookieManager) {
                        try {
                            com.sun.webkit.network.CookieManager cookieManager = (com.sun.webkit.network.CookieManager) cookieHandler;
                            Field field = cookieManager.getClass().getDeclaredField("store");
                            field.setAccessible(true);
                            Object obj = field.get(cookieManager);
                            System.out.println(obj);
                            Field field2 = obj.getClass().getDeclaredField("buckets");
                            field2.setAccessible(true);
                            Map<String,Map<Object,Object>> obj2 = (Map<String, Map<Object, Object>>) field2.get(obj);
                            for (Map.Entry<String, Map<Object, Object>> entry : obj2.entrySet()) {
                                System.out.println(entry.getKey());
                                Map<Object, Object> value = entry.getValue();
                                for (Map.Entry<Object, Object> entry2 : value.entrySet()) {
                                    System.out.println(entry2.getKey());
                                    System.out.println(entry2.getValue());
                                }
                                System.out.println("=======================");
                            }
                        } catch (Exception e) {
                            e.printStackTrace();
                        }

                    }
//                    CookieManager cookieManager = (CookieManager) CookieHandler.getDefault();
//                    CookieStore cookieJar = cookieManager.getCookieStore();
                    List<HttpCookie> cookies = cookieStore.getCookies();
                    for (HttpCookie cookie : cookies) {
                        System.out.println(cookie.getName() + ", " + cookie.getValue() + "," + cookie.getSecure());
                    }
                });

        // load the home page
        webEngine.load("https://abs-in.idumiao.com");

        //addNewStage components
        getChildren().add(toolBar);
        getChildren().add(browser);
    }

    // JavaScript interface object
    public class JavaApp {

        public void exit() {
            Platform.exit();
        }
    }

    private Node createSpacer() {
        Region spacer = new Region();
        HBox.setHgrow(spacer, Priority.ALWAYS);
        return spacer;
    }

    @Override
    protected void layoutChildren() {
        double w = getWidth();
        double h = getHeight();
        double tbHeight = toolBar.prefHeight(w);
        layoutInArea(browser,0,0,w,h-tbHeight,0,HPos.CENTER,VPos.CENTER);
        layoutInArea(toolBar,0,h-tbHeight,w,tbHeight,0,HPos.CENTER,VPos.CENTER);
    }

    @Override
    protected double computePrefWidth(double height) {
        return 900;
    }

    @Override
    protected double computePrefHeight(double width) {
        return 600;
    }
}

class MyCookieStore implements CookieStore {
    private Map<URI, List<HttpCookie>> map = new HashMap();

    public void add(URI uri, HttpCookie cookie) {
        List<HttpCookie> cookies = map.get(uri);
        if (cookies == null) {
            cookies = new ArrayList();
            map.put(uri, cookies);
        }
        System.out.println("add cookie:" + cookie.getName() + ", " + cookie.getValue() + "," + cookie.getDomain() + ", " + cookie.getSecure() + "," + cookies.contains(cookie));
        if (!cookies.contains(cookie)) {
            cookies.add(cookie);
        }
    }
    public List get(URI uri) {
        List cookies = map.get(uri);
        if (cookies == null) {
            cookies = new ArrayList ();
            map.put(uri, cookies);
        }
        return cookies;
    }

    public List getCookies() {
        Collection<List<HttpCookie>> values = map.values();
        List result = new ArrayList ();
        for (List value : values) {
            result.addAll(value);
        }
        return result;
    }

    public List getURIs() {
        Set keys = map.keySet();
        return new ArrayList (keys);
    }

    public boolean remove(URI uri, HttpCookie cookie) {
        List cookies = map.get(uri);
        if (cookies == null) {
            return false;
        }
        System.out.println("remove cookie:" + cookie.getName());
        return cookies.remove(cookie);
    }

    public boolean removeAll() {
        map.clear();
        return true;
    }
}

class MyCookiePolicy implements CookiePolicy {
    public boolean shouldAccept(URI uri, HttpCookie cookie) {
        return true;
    }
}

class InMemoryCookieStore implements CookieStore {
    // the in-memory representation of cookies
    private List<HttpCookie> cookieJar = null;

    // the cookies are indexed by its domain and associated uri (if present)
    // CAUTION: when a cookie removed from main data structure (i.e. cookieJar),
    //          it won't be cleared in domainIndex & uriIndex. Double-check the
    //          presence of cookie when retrieve one form index store.
    private Map<String, List<HttpCookie>> domainIndex = null;
    private Map<URI, List<HttpCookie>> uriIndex = null;

    // use ReentrantLock instead of syncronized for scalability
    private ReentrantLock lock = null;


    /**
     * The default ctor
     */
    public InMemoryCookieStore() {
        cookieJar = new ArrayList<HttpCookie>();
        domainIndex = new HashMap<String, List<HttpCookie>>();
        uriIndex = new HashMap<URI, List<HttpCookie>>();

        lock = new ReentrantLock(false);
    }

    /**
     * Add one cookie into cookie store.
     */
    public void add(URI uri, HttpCookie cookie) {
        // pre-condition : argument can't be null
        if (cookie == null) {
            throw new NullPointerException("cookie is null");
        }


        lock.lock();
        try {
            // remove the ole cookie if there has had one
            cookieJar.remove(cookie);

            // add new cookie if it has a non-zero max-age
            if (cookie.getMaxAge() != 0) {
                System.out.println("add cookie:" + cookie.getName() + ", " + cookie.getValue() + "," + cookie.getDomain() + ", " + cookie.getSecure());

                cookieJar.add(cookie);
                // and add it to domain index
                if (cookie.getDomain() != null) {
                    addIndex(domainIndex, cookie.getDomain(), cookie);
                }
                if (uri != null) {
                    // add it to uri index, too
                    addIndex(uriIndex, getEffectiveURI(uri), cookie);
                }
            }
        } finally {
            lock.unlock();
        }
    }


    /**
     * Get all cookies, which:
     *  1) given uri domain-matches with, or, associated with
     *     given uri when added to the cookie store.
     *  3) not expired.
     * See RFC 2965 sec. 3.3.4 for more detail.
     */
    public List<HttpCookie> get(URI uri) {
        // argument can't be null
        if (uri == null) {
            throw new NullPointerException("uri is null");
        }

        List<HttpCookie> cookies = new ArrayList<HttpCookie>();
        boolean secureLink = "https".equalsIgnoreCase(uri.getScheme());
        lock.lock();
        try {
            // check domainIndex first
            getInternal1(cookies, domainIndex, uri.getHost(), secureLink);
            // check uriIndex then
            getInternal2(cookies, uriIndex, getEffectiveURI(uri), secureLink);
        } finally {
            lock.unlock();
        }

        return cookies;
    }

    /**
     * Get all cookies in cookie store, except those have expired
     */
    public List<HttpCookie> getCookies() {
        List<HttpCookie> rt;

        lock.lock();
        try {
            Iterator<HttpCookie> it = cookieJar.iterator();
            while (it.hasNext()) {
                if (it.next().hasExpired()) {
                    it.remove();
                }
            }
        } finally {
            rt = Collections.unmodifiableList(cookieJar);
            lock.unlock();
        }

        return rt;
    }

    /**
     * Get all URIs, which are associated with at least one cookie
     * of this cookie store.
     */
    public List<URI> getURIs() {
        List<URI> uris = new ArrayList<URI>();

        lock.lock();
        try {
            Iterator<URI> it = uriIndex.keySet().iterator();
            while (it.hasNext()) {
                URI uri = it.next();
                List<HttpCookie> cookies = uriIndex.get(uri);
                if (cookies == null || cookies.size() == 0) {
                    // no cookies list or an empty list associated with
                    // this uri entry, delete it
                    it.remove();
                }
            }
        } finally {
            uris.addAll(uriIndex.keySet());
            lock.unlock();
        }

        return uris;
    }


    /**
     * Remove a cookie from store
     */
    public boolean remove(URI uri, HttpCookie ck) {
        // argument can't be null
        if (ck == null) {
            throw new NullPointerException("cookie is null");
        }

        boolean modified = false;
        lock.lock();
        try {
            modified = cookieJar.remove(ck);
        } finally {
            lock.unlock();
        }

        return modified;
    }


    /**
     * Remove all cookies in this cookie store.
     */
    public boolean removeAll() {
        lock.lock();
        try {
            if (cookieJar.isEmpty()) {
                return false;
            }
            cookieJar.clear();
            domainIndex.clear();
            uriIndex.clear();
        } finally {
            lock.unlock();
        }

        return true;
    }


    /* ---------------- Private operations -------------- */


    /*
     * This is almost the same as HttpCookie.domainMatches except for
     * one difference: It won't reject cookies when the 'H' part of the
     * domain contains a dot ('.').
     * I.E.: RFC 2965 section 3.3.2 says that if host is x.y.domain.com
     * and the cookie domain is .domain.com, then it should be rejected.
     * However that's not how the real world works. Browsers don't reject and
     * some sites, like yahoo.com do actually expect these cookies to be
     * passed along.
     * And should be used for 'old' style cookies (aka Netscape type of cookies)
     */
    private boolean netscapeDomainMatches(String domain, String host)
    {
        if (domain == null || host == null) {
            return false;
        }

        // if there's no embedded dot in domain and domain is not .local
        boolean isLocalDomain = ".local".equalsIgnoreCase(domain);
        int embeddedDotInDomain = domain.indexOf('.');
        if (embeddedDotInDomain == 0) {
            embeddedDotInDomain = domain.indexOf('.', 1);
        }
        if (!isLocalDomain && (embeddedDotInDomain == -1 || embeddedDotInDomain == domain.length() - 1)) {
            return false;
        }

        // if the host name contains no dot and the domain name is .local
        int firstDotInHost = host.indexOf('.');
        if (firstDotInHost == -1 && isLocalDomain) {
            return true;
        }

        int domainLength = domain.length();
        int lengthDiff = host.length() - domainLength;
        if (lengthDiff == 0) {
            // if the host name and the domain name are just string-compare euqal
            return host.equalsIgnoreCase(domain);
        } else if (lengthDiff > 0) {
            // need to check H & D component
            String H = host.substring(0, lengthDiff);
            String D = host.substring(lengthDiff);

            return (D.equalsIgnoreCase(domain));
        } else if (lengthDiff == -1) {
            // if domain is actually .host
            return (domain.charAt(0) == '.' &&
                    host.equalsIgnoreCase(domain.substring(1)));
        }

        return false;
    }

    private void getInternal1(List<HttpCookie> cookies, Map<String, List<HttpCookie>> cookieIndex,
                              String host, boolean secureLink) {
        // Use a separate list to handle cookies that need to be removed so
        // that there is no conflict with iterators.
        ArrayList<HttpCookie> toRemove = new ArrayList<HttpCookie>();
        for (Map.Entry<String, List<HttpCookie>> entry : cookieIndex.entrySet()) {
            String domain = entry.getKey();
            List<HttpCookie> lst = entry.getValue();
            for (HttpCookie c : lst) {
                if ((c.getVersion() == 0 && netscapeDomainMatches(domain, host)) ||
                        (c.getVersion() == 1 && HttpCookie.domainMatches(domain, host))) {
                    if ((cookieJar.indexOf(c) != -1)) {
                        // the cookie still in main cookie store
                        if (!c.hasExpired()) {
                            // don't add twice and make sure it's the proper
                            // security level
                            if ((secureLink || !c.getSecure()) &&
                                    !cookies.contains(c)) {
                                cookies.add(c);
                            }
                        } else {
                            toRemove.add(c);
                        }
                    } else {
                        // the cookie has beed removed from main store,
                        // so also remove it from domain indexed store
                        toRemove.add(c);
                    }
                }
            }
            // Clear up the cookies that need to be removed
            for (HttpCookie c : toRemove) {
                lst.remove(c);
                cookieJar.remove(c);

            }
            toRemove.clear();
        }
    }

    // @param cookies           [OUT] contains the found cookies
    // @param cookieIndex       the index
    // @param comparator        the prediction to decide whether or not
    //                          a cookie in index should be returned
    private <T> void getInternal2(List<HttpCookie> cookies,
                                  Map<T, List<HttpCookie>> cookieIndex,
                                  Comparable<T> comparator, boolean secureLink)
    {
        for (T index : cookieIndex.keySet()) {
            if (comparator.compareTo(index) == 0) {
                List<HttpCookie> indexedCookies = cookieIndex.get(index);
                // check the list of cookies associated with this domain
                if (indexedCookies != null) {
                    Iterator<HttpCookie> it = indexedCookies.iterator();
                    while (it.hasNext()) {
                        HttpCookie ck = it.next();
                        if (cookieJar.indexOf(ck) != -1) {
                            // the cookie still in main cookie store
                            if (!ck.hasExpired()) {
                                // don't add twice
                                if ((secureLink || !ck.getSecure()) &&
                                        !cookies.contains(ck))
                                    cookies.add(ck);
                            } else {
                                it.remove();
                                cookieJar.remove(ck);
                            }
                        } else {
                            // the cookie has beed removed from main store,
                            // so also remove it from domain indexed store
                            it.remove();
                        }
                    }
                } // end of indexedCookies != null
            } // end of comparator.compareTo(index) == 0
        } // end of cookieIndex iteration
    }

    // add 'cookie' indexed by 'index' into 'indexStore'
    private <T> void addIndex(Map<T, List<HttpCookie>> indexStore,
                              T index,
                              HttpCookie cookie)
    {
        if (index != null) {
            List<HttpCookie> cookies = indexStore.get(index);
            if (cookies != null) {
                // there may already have the same cookie, so remove it first
                cookies.remove(cookie);

                cookies.add(cookie);
            } else {
                cookies = new ArrayList<HttpCookie>();
                cookies.add(cookie);
                indexStore.put(index, cookies);
            }
        }
    }


    //
    // for cookie purpose, the effective uri should only be http://host
    // the path will be taken into account when path-match algorithm applied
    //
    private URI getEffectiveURI(URI uri) {
        URI effectiveURI = null;
        try {
            effectiveURI = new URI("http",
                    uri.getHost(),
                    null,  // path component
                    null,  // query component
                    null   // fragment component
            );
        } catch (URISyntaxException ignored) {
            effectiveURI = uri;
        }

        return effectiveURI;
    }
}

